package cn.heiliu.selenium.service.impl;

import cn.heiliu.selenium.config.SetupConfig;
import cn.heiliu.selenium.domain.WebDriverStatus;
import cn.heiliu.selenium.exception.NoWebDriverException;
import cn.heiliu.selenium.service.ISeleniumPoolService;
import cn.heiliu.selenium.service.IWebDriverCreateService;
import cn.heiliu.selenium.service.IWebDriverService;
import cn.hutool.core.collection.ConcurrentHashSet;
import cn.hutool.core.thread.ThreadFactoryBuilder;
import cn.hutool.core.thread.ThreadUtil;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.openqa.selenium.TimeoutException;

import java.util.Random;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * selenium 连接池实现
 * @author wangyelou
 * @date 2021/6/14
 */
@Slf4j
public class SeleniumPoolServiceImpl implements ISeleniumPoolService, Runnable {

    /**
     * 生成webdriver对象
     */
    private IWebDriverCreateService webDriverCreateService;
    private SetupConfig setupConfig;

    private ConcurrentHashSet<IWebDriverService> pool;

    /**
     * 有效webdriver个数
     */
    @Getter
    private int validNum = 0;

    public SeleniumPoolServiceImpl(IWebDriverCreateService webDriverCreateService, SetupConfig setupConfig) {
        this.webDriverCreateService = webDriverCreateService;
        this.setupConfig = setupConfig;
        pool = new ConcurrentHashSet<>(setupConfig.getPoolSize());
        // 后台检测线程
        ThreadPoolExecutor monitor = new ThreadPoolExecutor(1, 1, 0, TimeUnit.SECONDS, new SynchronousQueue<>(),
                ThreadFactoryBuilder.create().setNamePrefix("selenium-pool-monitor-").build());
        monitor.execute(this);
    }

    @Override
    public synchronized IWebDriverService getWebDriver(long timeout) throws TimeoutException {
        // 阻塞等待
        long startTime = System.currentTimeMillis();
        while (validNum <= 0) {
            if (System.currentTimeMillis() - startTime >= timeout) {
                throw new TimeoutException();
            }
            if (pool.size() < setupConfig.getPoolSize()) {
                //新建浏览器
                pool.add(webDriverCreateService.createWebDriver(setupConfig));
                validNum ++;
            } else {
                try {
                    this.wait(timeout);
                } catch (InterruptedException e) {
                    throw new TimeoutException();
                }
            }
        }

        // 获取 webdriver
        for (IWebDriverService webDriver: pool) {
            // 有效 且 不在使用
            if (webDriver.getStatus().isValid() && !webDriver.getStatus().isUsed()) {
                validNum --;
                webDriver.getStatus().startUse();
                return webDriver;
            }
        }
        throw new NoWebDriverException(String.format("pool size %s, valid num %s, but no valid webdriver", pool.size(), validNum));
    }

    @Override
    public synchronized void release(IWebDriverService webDriver) {
        WebDriverStatus status = webDriver.getStatus();
        if (!status.isValid() || status.getFailedNum() > setupConfig.getMaxFailedNum()) {
            log.info("{} invalid, remove", webDriver.getFlag());
            status.invalid();
            validNum --;
            pool.remove(webDriver);
            webDriver.quit();
        } else {
            status.release();
            validNum ++;
        }
        this.notifyAll();
    }

    @Override
    public void run() {
        Random random = new Random();
        while (true) {
            for (IWebDriverService webDriver: pool) {
                WebDriverStatus status = webDriver.getStatus();

                if (!status.isValid()) {
                    synchronized (this) {
                        log.warn("{} checked invalid, go to remove", webDriver.getFlag());
                        release(webDriver);
                    }
                }

                if (status.getStartUseTime() - System.currentTimeMillis() > 300000) {
                    log.warn("{} used more than {}", webDriver.getFlag(), status.getStartUseTime() - System.currentTimeMillis());
                }

                // 运行一段时间后关闭， 防止浏览器内存问题， 30分钟上下5分钟，防止同时关闭/启动
                if (System.currentTimeMillis() - status.getStartUpTime() > random.nextInt(300000) + 1800000) {
                    synchronized (this) {
                        if (!webDriver.getStatus().isUsed()) {
                            webDriver.getStatus().invalid();
                            release(webDriver);
                        }
                    }
                }

            }

            ThreadUtil.sleep(20000);
        }
    }
}
